# =============================================================================
# ** Input Module disablement
# =============================================================================
# Hawk-McKain
# Version 2
# 16.04.07 (dd.mm.yy)
# == About: ===================================================================
# This Input Module is designed as a replacement for the built-in
# version, based off version 5 of NearFantastica's Keyboard Module.
# 
# Using HawkPut is simple because it uses all the same functions as the 
# official Input Module but gives you access to all of the keys available and
# the ability to bind keys together. There is also support for Numlock.
#
# == Instructions: ============================================================
# As I said before, all basic functions act the same.
# 	Input.press?(Input::G)
# 	Input.trigger?(Input::P)
# 	Input.repeat?(Input:DOWN)
#
# New functions include:
#	Input.trigger?(Input:P, delete=false)
# 		- Will return true if P is triggered.
#		- Will return true to a future trigger? check as well.
#
# 	Input::J.bindTo(Input::DOWN)
#		- Pressing J will trigger Down
#		- Input.trigger?(Input::J) will still return true.
#
# 	Input::P.bindTo(Input::I, disable=true) 
# 		- Pressing P will trigger I
# 		- Input.trigger?(Input::P) will not return true.
#
#	Input::Mouse_Left.bind(Input::C, clear=true)
# 		- A synonym for Input::C.bindTo(Input::Mouse_Left)
#		- Will clear any other keys that are bound to Mouse_left
#
# 	Input::J.unbindFrom(Input::DOWN)
# 		- Unbinds J from DOWN
#
#	Input::P.unbindFrom(Input::I, enable=false)
# 		- Unbinds P from I and leaves it disabled.
#
#	Input::O.disable
# 		- Input.trigger(Input::O) won't return true until O is enabled again.
#
# == Change Log: ==============================================================
# Version 2.0
#	Initial Release
# =============================================================================

#------------------------------------------------------------------------------
# * SDK Log Script
#------------------------------------------------------------------------------
SDK.log("HawkPut", "Hawk-McKain", 2, "16.04.2008")
SDK.check_requirements(2.3, [], {'Win32::Registry' => 1.0})

#------------------------------------------------------------------------------
# *Check SDK Requirements
#------------------------------------------------------------------------------
#unless SDK.enabled?('Win32::Registry')
#	p "The Win32::Registry script isn't in the game.", "HawkPut has been disabled."
#	SDK.disable("HawkPut")
#end

#------------------------------------------------------------------------------
# * Begin SDK Enable Test
#------------------------------------------------------------------------------
if SDK.enabled?("HawkPut")
		
	module Input
		#--------------------------------------------------------------------------
		# Developer Options
		#--------------------------------------------------------------------------
		# Sets the numpad key setup based on the number.
		# :disabled - Forces Numlock to appear off.
		# false - Checks if Numlock is on or not.
		# true - Forces Numlock to appear on.
		# Default: false
		StickyNumlock = false
		
		# If set to false it won't retrieve the bindings from the registry.
		# Default: true
		ReadReg = true
		
		# Lets you change the location of the key binding string
		# Default: RMXP = "RGSS"; RMVX = "RGSS2"
		RegKey = "RGSS"
		
		# The number of frames between each "repeat?" keystroke.
		# Default: (Graphics.frame_rate / 5)
		RepeatDelay = (Graphics.frame_rate / 5)
		
		# A list of all input related modules that should be updated along with the
		# main input module. The idea being to improve Plug 'n' Play ability.
		# Default: [[Mouse, 'Mouse Input']]
		UpdateList = [[Mouse, 'Mouse Input']]

		def self.inputUpdates
			for mod, name in UpdateList
				mod.update if SDK.enabled?(name)
				#return
			end
		end
		
	#--------------------------------------------------------------------------
	# Key Object
	#--------------------------------------------------------------------------
	# Stores information about each key and provides convienience methods
	# for things like setting bindings.
	#--------------------------------------------------------------------------
		class KeyObject		 
			attr_reader		:id
			attr_reader		:name
			attr_accessor 	:boundto
			attr_accessor 	:timer
			attr_accessor 	:binds
			attr_accessor 	:triggered
			attr_accessor 	:repeating
			attr_accessor 	:disabled	
		
			def initialize(key_id, key_name)
				@id = key_id
				@name = key_name
				@timer = 0
				@binds = []
				@boundto = []
				@triggered = false
				@repeating = false
				# If 'disabled' is true you can't check the state of this key.
				@disabled = false
			end
			
			def inspect
				return "#{@name}(#{id}) - Binds: [#{@binds}] - Bound To: [#{@boundto}]"
			end
			
			def to_s
				return "#{@name}(#{id})"
			end
			
			def disable
				@disabled = true
			end
			
			def enable
				@disabled = false
			end
					
			# Convience Operator for bindTo
			def +(new_key)
				if new_key.is_a?(KeyObject)
					self.bindTo(new_key)
				end
			end

			#------------------------------------------------------------------------
			# Binds 'key' to this key.
			# ex. Input::P.bind(Input::I) means pressing I triggers P
			#------------------------------------------------------------------------
			def bind(new_key, clear=false)
				# Return false if 'new_key' is already bound to this key or is this key.
				if @binds.include?(new_key) or new_key == self
					return false 
				end
				# p "Binding #{key.name}(#{key.id}) to #{@name}(#{@id})"
				# Make sure 'key' knows it's being bound.
				new_key.boundto.push(self)
				@binds.clear if clear
				@binds.push(new_key)
				return true
			end
			
			#------------------------------------------------------------------------
			# Binds this key to 'new_key'
			# ex. Input::P.bindTo(Input::I) means pressing P triggers I
			#------------------------------------------------------------------------
			def bindTo(new_key, disable=false)
				# Return false if this key is already bound to 'new_key'.
				return false if @boundto.include?(new_key)
				self.disable() if disable
				return new_key.bind(self)
			end
			
			# Convience Operator for unbindFrom
			def -(old_key)
				if new_key.is_a?(KeyObject)
					self.unbindFrom(old_key)
				end
			end

			#------------------------------------------------------------------------
			# Unbinds 'old_key' from this key.
			#------------------------------------------------------------------------
			def unbind(old_key)
				# Return false if 'old_key' wasn't bound to this key.
				return false if not @binds.delete(old_key)
				old_key.boundto.delete(self)
				return true
			end
				
			#------------------------------------------------------------------------
			# Binds this key to 'new_key'
			#------------------------------------------------------------------------
			def unbindFrom(old_key, enable=true)
				# Return false if this key isn't already bound to 'old_key'.
				if not old_key.unbind(self)
				 return false
				end
			 
				self.enable() if (@boundto.empty? and enable)
				return true
			end

			#------------------------------------------------------------------------
			# Return an array of keys bound to this key.
			#------------------------------------------------------------------------
			def returnBindings
				binds = []
				binds.push(self) unless @disabled
								
				if Input::Trans_tables['numpad'].include?(self)
					if Input::StickyNumlock != :disabled 
					elsif (Input::StickyNumlock | Input.toggled?(Input::Numlock))
						binds.push(Input::Trans_tables['numpad'][self])
					end
				end
				
				return binds | @binds
			end
		end
	end
		
	module Input						
		#--------------------------------------------------------------------------
		# Constants Setup
		#--------------------------------------------------------------------------
	
		# Support for official inputs.	 
		# Meaning it can be implemented without any changes to the base code.
		DOWN = KeyObject.new(40, "DOWN")
		LEFT = KeyObject.new(37, "LEFT")
		RIGHT = KeyObject.new(39, "RIGHT")
		UP = KeyObject.new(38, "UP")
		A = KeyObject.new(65, "A")
		B = KeyObject.new(66, "B")
		C = KeyObject.new(67, "C")
		X = KeyObject.new(88, "X")
		Y = KeyObject.new(89, "Y")
		Z = KeyObject.new(90, "Z")
		L = KeyObject.new(76, "L")
		R = KeyObject.new(82, "R")
		SHIFT = KeyObject.new(16, "SHIFT")
		CTRL = KeyObject.new(17, "CTRL")
		ALT = KeyObject.new(18, "ALT")
		F5 = KeyObject.new(116, "F5")
		F6 = KeyObject.new(117, "F6")
		F7 = KeyObject.new(118, "F7")
		F8 = KeyObject.new(119, "F8")
		F9 = KeyObject.new(120, "F9")
		Official = [
			A, B, C, X, Y, Z, L, R
		]
		
		# All the various new inputs.
		# "Duplicates" are left here just to look nice.
		None_Key = KeyObject.new(0, "None Key")
		Mouse_Left = KeyObject.new(1, "Mouse_Left")
		Mouse_Right = KeyObject.new(2, "Mouse_Right")
		Mouse_Middle = KeyObject.new(4, "Mouse_Middle")
		Mouse_4 = KeyObject.new(5, "Mouse_4")
		Mouse_5 = KeyObject.new(6, "Mouse_5")
		Backspace = KeyObject.new(8, "Backspace")
		Tab = KeyObject.new(9, "Tab")
		Enter = KeyObject.new(13, "Enter")
		Shift = SHIFT
		LShift = KeyObject.new(160, "LShift")
		RShift = KeyObject.new(161, "RShift")
		Ctrl = CTRL
		LCtrl = KeyObject.new(162, "LCtrl")
		RCtrl = KeyObject.new(163, "RCtrl")
		Alt = ALT
		LAlt = KeyObject.new(164, "LAlt")
		RAlt = KeyObject.new(165, "RAlt")
		Esc = KeyObject.new(27, "Esc")
		Space = KeyObject.new(32, "Space")
		Down = DOWN
		Left = LEFT
		Right = RIGHT
		Up = UP
		NonAlphaNum = [
			Mouse_Left, Mouse_Right, Mouse_Middle, Mouse_4, Mouse_5,
			Backspace, Tab, Enter, Shift, LShift, RShift, Ctrl, LCtrl, RCtrl, 
			Alt, LAlt, RAlt, Esc, Space, Down, Left, Right, Up
		]
		
		Capslock = KeyObject.new(20, "Capslock")		 
		Numlock = KeyObject.new(144, "Numlock")
		Scrolllock = KeyObject.new(145, "Scrolllock")
		Toggle = [
			Capslock, Numlock, Scrolllock
		] 
		
		Numpad0 = KeyObject.new(45, "Numpad0")
		Numpad1 = KeyObject.new(35, "Numpad1")
		Numpad2 = DOWN#KeyObject.new(40, "Numpad2")
		Numpad3 = KeyObject.new(34, "Numpad3")
		Numpad4 = LEFT#KeyObject.new(37, "Numpad4")
		Numpad5 = KeyObject.new(12, "Numpad5")
		Numpad6 = RIGHT#KeyObject.new(39, "Numpad6")
		Numpad7 = KeyObject.new(36, "Numpad7")
		Numpad8 = UP#KeyObject.new(38, "Numpad8")
		Numpad9 = KeyObject.new(33, "Numpad9")
		AltNumpad0 = KeyObject.new(96, "AltNumpad0")
		AltNumpad1 = KeyObject.new(97, "AltNumpad1")
		AltNumpad2 = KeyObject.new(98, "AltNumpad2")
		AltNumpad3 = KeyObject.new(99, "AltNumpad3")
		AltNumpad4 = KeyObject.new(100, "AltNumpad4")
		AltNumpad5 = KeyObject.new(101, "AltNumpad5")
		AltNumpad6 = KeyObject.new(102, "AltNumpad6")
		AltNumpad7 = KeyObject.new(103, "AltNumpad7")
		AltNumpad8 = KeyObject.new(104, "AltNumpad8")
		AltNumpad9 = KeyObject.new(105, "AltNumpad9")			 
		NumpadForwardslash = KeyObject.new(111, "NumpadForwardslash")
		NumpadPeriod = KeyObject.new(110, "NumpadPeriod")
		NumpadDash = KeyObject.new(109, "NumpadDash")
		NumpadPlus = KeyObject.new(107, "NumpadPlus")
		NumpadAsterisk = KeyObject.new(106, "NumpadAsterisk")
		Numpad = [
			Numpad0, Numpad1, Numpad2, Numpad3,
			Numpad4, Numpad5, Numpad6, Numpad7,
			Numpad8, Numpad9, NumpadForwardslash, 
			NumpadPeriod, NumpadDash, NumpadPlus,
			NumpadAsterisk
		]
											
		Numkey0 = KeyObject.new(48, "Numkey0")
		Numkey1 = KeyObject.new(49, "Numkey1")
		Numkey2 = KeyObject.new(50, "Numkey2")
		Numkey3 = KeyObject.new(51, "Numkey3")
		Numkey4 = KeyObject.new(52, "Numkey4")
		Numkey5 = KeyObject.new(53, "Numkey5")
		Numkey6 = KeyObject.new(54, "Numkey6")
		Numkey7 = KeyObject.new(55, "Numkey7")
		Numkey8 = KeyObject.new(56, "Numkey8")
		Numkey9 = KeyObject.new(57, "Numkey9")
		Numbers = [
			Numkey0, Numkey1, Numkey2, 
			Numkey3, Numkey4, Numkey5, 
			Numkey6, Numkey7, Numkey8,
			Numkey9
		]
											
		# A = KeyObject.new(65, "A")
		# B = KeyObject.new(66, "B")
		# C = KeyObject.new(67, "C")
		D = KeyObject.new(68, "D")
		E = KeyObject.new(69, "E")
		F = KeyObject.new(70, "F")
		G = KeyObject.new(71, "G")
		H = KeyObject.new(72, "H")
		I = KeyObject.new(73, "I")
		J = KeyObject.new(74, "J")
		K = KeyObject.new(75, "K")
		# L = KeyObject.new(76, "L")
		M = KeyObject.new(77, "M")
		N = KeyObject.new(78, "N")
		O = KeyObject.new(79, "O")
		P = KeyObject.new(80, "P")
		Q = KeyObject.new(81, "Q")
		# R = KeyObject.new(82, "R")
		S = KeyObject.new(83, "S")
		T = KeyObject.new(84, "T")
		U = KeyObject.new(85, "U")
		V = KeyObject.new(86, "V")
		W = KeyObject.new(87, "W")
		# X = KeyObject.new(88, "X")
		# Y = KeyObject.new(89, "Y")
		# Z = KeyObject.new(90, "Z")
		Letters = [
			D, E, F, G, H, I, J, K, M, 
			N, O, P, Q, S, T, U, V, W
		]							 
										
		F1 = KeyObject.new(112, "F1")
		F2 = KeyObject.new(113, "F2")
		F3 = KeyObject.new(114, "F3")
		F4 = KeyObject.new(115, "F4")
		#F5 = KeyObject.new(116, "F5")
		#F6 = KeyObject.new(117, "F6")
		#F7 = KeyObject.new(118, "F7")
		#F8 = KeyObject.new(119, "F8")
		#F9 = KeyObject.new(120, "F9")
		F10 = KeyObject.new(121, "F10")
		F11 = KeyObject.new(122, "F11")
		F12 = KeyObject.new(123, "F12")
		Fkeys = [
			F1, F2, F3, F4, F5, F6, 
			F7, F8, F9, F10, F11, F12
		]				 

		Collon = KeyObject.new(186, "Collon")
		Equal = KeyObject.new(187, "Equal")
		Comma = KeyObject.new(188, "Comma")
		Underscore = KeyObject.new(189, "Underscore")
		Period = KeyObject.new(190, "Period")
		Forwardslash = KeyObject.new(191, "Forwardslash")
		LBracket = KeyObject.new(219, "LBracket")
		RBracket = KeyObject.new(221, "RBracket")
		Backslash = KeyObject.new(220, "Backslash")
		Quote = KeyObject.new(222, "Quote")
		Punc = [
			Collon, Equal, Comma, Underscore,
			Period, Forwardslash, LBracket, RBracket,
			Backslash, Quote
		]
		
		# group arrays
		@button_hashes = [Official, Numpad, NonAlphaNum, Toggle, 
											Numbers, Letters, Fkeys, Punc]
		
		# A list of each key code in order of the F1 list.
		#binding_table = [32, 13, 27, 96, 16, 90, 88,
		#				67, 86, 66, 65, 83, 68, 81, 78]
		@binding_table = [
			Space, Enter, Esc, Numpad0, Shift, 
			Z, X, C, V, B, A, S, D, Q, W
		]
		# Stores bindings for comparing
		@bindings = []
	
		# Conversation hashes for various things.
		Trans_tables = {
			# Create a F1 Key -> Official binding table.
			'official' => {},
			# Translates the keycodes from the registry
			'registry' => {
				"\v" => A, #65
				"\f" => B, #66
				"\r" => C, #67
				"\016" => X, #88
				"\017" => Y, #89
				"\020" => Z, #90
				"\021" => L, #67
				"\022" => R, #82
				"\000" => None_Key #0
			},
			# Converts the codes from "Condition Branch"
			# to their proper keycodes.
			'branch' => {
				2 => DOWN,
				4 => LEFT,
				6 => RIGHT,
				8 => UP,
				11 => A,
				12 => B,
				13 => C,
				14 => X,
				15 => Y,
				16 => Z,
				17 => L,
				18 => R
			},
			# Converts the numpad when numlock is on.
			'numpad' => {
				Numpad0	=> AltNumpad0,
				Numpad1	=> AltNumpad1,
				Numpad2	=> AltNumpad2,
				Numpad3	=> AltNumpad3,
				Numpad4	=> AltNumpad4,
				Numpad5	=> AltNumpad5,
				Numpad6	=> AltNumpad6,
				Numpad7	=> AltNumpad7,
				Numpad8	=> AltNumpad8,
				Numpad9	=> AltNumpad9
			}					
		}
		
		# Define API calls.
		GetKeyState = Win32API.new("user32","GetKeyState",['i'],'i')
		GetAsyncKeyState = Win32API.new("user32","GetAsyncKeyState",['i'],'i')

#--------------------------------------------------------------------------
# Useable Commands
#--------------------------------------------------------------------------

		#--------------------------------------------------------------------------
		# Returns true once every time a key is pressed.
		#--------------------------------------------------------------------------
		def Input.trigger?(key, delete = false)
			for key in key.returnBindings()
				if key.triggered == true
					key.triggered = :disabled if delete
					return true 
				end
			end
			return false
		end
		
		#--------------------------------------------------------------------------
		# Sends the keystroke every RepeatDelay frames.
		#--------------------------------------------------------------------------
		def Input.repeat?(key)
			for key in key.returnBindings()
				if key.repeating
					key.repeating = false
					return true 
				end
			end
			return false
		end
		
		#--------------------------------------------------------------------------
		# Returns true if the key is held down.
		#--------------------------------------------------------------------------
		def Input.press?(key)
			# Support for Conditional Branches
			 
			begin
				if $game_system.map_interpreter.running?
					key = Trans_tables['branch'].fetch(key, key)
				end
			rescue
			end
			
			for key in key.returnBindings()
				return true if self.key_down?(key)
			end
			return false
		end

		#--------------------------------------------------------------------------
		# Return a number depending on which directional key is pressed.
		#--------------------------------------------------------------------------
		def Input.dir4
			return 2 if Input.press?(Input::DOWN)
			return 4 if Input.press?(Input::LEFT)
			return 6 if Input.press?(Input::RIGHT)
			return 8 if Input.press?(Input::UP)
			return 0
		end

		def Input.dir8
			return 1 if (Input.press?(Input::DOWN) and Input.press?(Input::LEFT)) or 
							Input.press?(Input::Numpad1)
			return 3 if (Input.press?(Input::DOWN) and Input.press?(Input::RIGHT)) or 
							Input.press?(Input::Numpad3)
			return 7 if (Input.press?(Input::UP) and Input.press?(Input::LEFT)) or 
							Input.press?(Input::Numpad3)
			return 9 if (Input.press?(Input::UP) and Input.press?(Input::RIGHT)) or 
							Input.press?(Input::Numpad9)
			return Input.dir4
		end
		
		#--------------------------------------------------------------------------
		# Update registry bindings.
		#--------------------------------------------------------------------------
		def Input.update
			updateBindings if ReadReg
			# Automatically update any other inputs you might have.
			inputUpdates
			
			# Note: Repeat checks must come first.
			# Unless you don't want to be official.
			for hash in @button_hashes
				for key in hash
					self.repeat_check(key)
					self.trigger_check(key)
				end
			end
		end
		
#--------------------------------------------------------------------------
# Internally Used Commands
#--------------------------------------------------------------------------
																						
		private
		
		#--------------------------------------------------------------------------	
		# Only returns true once each time a key is pressed.
		#--------------------------------------------------------------------------			
		def self.trigger_check(key)
			if key.triggered and not self.key_down?(key)
				key.triggered = false
			elsif self.key_down?(key)
				# Sets key.trigger to true for a single frame.
				# Every frame after, until it's released, it is set to :disabled
				key.triggered = key.triggered ? :disabled : true 
				return true
			end
			return false
		end
		
		#--------------------------------------------------------------------------	
		# Times how often to register a key stroke.
		#--------------------------------------------------------------------------	
		def self.repeat_check(key)
			if self.key_down?(key)
				key.timer -= 1 if key.timer > 0
				if key.timer == 0
					key.repeating = true
					key.timer = (key.triggered ? RepeatDelay : (RepeatDelay + 25))
					return true
				end
			elsif key.timer != 0
				key.timer = 0
			end
			return false
		end

		
		#--------------------------------------------------------------------------	
		# Load the user defined bindings from the registry
		# Note: It currently doesn't support game pads.
		# Also Note: The registry is only updated after you close the game, 
		#	 this means changes made from the F1 menu won't show until you restart.
		#--------------------------------------------------------------------------	
		def self.updateBindings
			subkey = Win32::Registry::HKEY_CURRENT_USER.open(
				"Software\\Enterbrain\\#{RegKey}"
			) 
			new_bindings = subkey.read('ButtonAssign', 3)[1].scan(/./)[10, 24]
			new_bindings.collect! {|k| Trans_tables['registry'].fetch(k)}
			# If nothing has changed, return and save cycles.
			return if new_bindings == @bindings
			
			#p ("New Bindings: #{new_bindings.join(' ')}", 
			#	"Old Bindings: #{@bindings.join(' ')}") #if not @bindings.empty?

			# Check for differences in the settings
			for i in 0...new_bindings.length
				# Load the variables into memory, speeds things up a bit
				orig = @bindings[i]; new = new_bindings[i]; key = @binding_table[i]
				# Only update differences
				next if orig == new
				key.unbindFrom(orig) if orig
				key.bindTo(new)
			end
			
			# Update the last binding settings with the new ones.
			@bindings = new_bindings
		end
		
		#--------------------------------------------------------------------------
		# Return 'true' if the key is down.
		#--------------------------------------------------------------------------
		def self.key_down?(key)
			return !GetKeyState.call(key.id).between?(0,1)
		end
		
		#--------------------------------------------------------------------------	
		# Return true if the key is toggled on.
		#--------------------------------------------------------------------------	
		def self.toggled?(key)
			(GetKeyState.call(key.id) & 1) == 1
		end

		#--------------------------------------------------------------------------
		# Check if the the key was pressed since the last check.
		#--------------------------------------------------------------------------
		def self.testkey(key)
			GetAsyncKeyState.call(key.id) & 1 == 1
		end
			
		public
		
	end
#------------------------------------------------------------------------------
# * End SDK Enable Test
#------------------------------------------------------------------------------
end 
